﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;

namespace Helper {
    /// <summary>
    /// Based on High-Performance Timer by Daniel Strigl.
    /// This class only does profiling in debug builds.
    /// http://www.codeproject.com/KB/cs/highperformancetimercshar.aspx
    /// </summary>
    public static class Profiler {
#if DEBUG
    [DllImport("Kernel32.dll")]
    private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

    [DllImport("Kernel32.dll")]
    private static extern bool QueryPerformanceFrequency(out long lpFrequency);

    private static int MAX_PROFILES = 20;
    private static int currentIndex;
    private static string[] profiles;
    private static int[] count;
    private static long[] startTime;
    private static double[] lastElapsed;
    private static double[] totalElapsed;
    private static Dictionary<string, int> indices;
    private static long stopTime;
    private static long freq;
    private static double inverseFreq;

    static Profiler() {

        if(!QueryPerformanceFrequency(out freq)) {
            throw new Win32Exception();
        } else {
            inverseFreq = (1d / freq);
            Reset();
        }
    }
#endif

        /// <summary>
        /// Resets all profiles.
        /// </summary>
        public static void Reset() {
#if DEBUG
        profiles = new string[MAX_PROFILES];
        count = new int[MAX_PROFILES];
        startTime = new long[MAX_PROFILES];
        lastElapsed = new double[MAX_PROFILES];
        totalElapsed = new double[MAX_PROFILES];
        indices = new Dictionary<string, int>(MAX_PROFILES);
        currentIndex = -1;
#endif
        }

        /// <summary>
        /// Resets a profile.
        /// </summary>
        /// <param name="profileName">Profile name.</param>
        public static void Reset(string profileName) {
#if DEBUG
        int index;

        if(indices.TryGetValue(profileName, out index)) {
            count[index] = 0;
            lastElapsed[index] = 0;
            totalElapsed[index] = 0;
        }
#endif
        }

        /// <summary>
        /// Stops all stored profiles.
        /// </summary>
        public static void StopAll() {
#if DEBUG
        for(int i = 0; i < currentIndex + 1; i++) {
            Stop(profiles[i]);
        }
#endif
        }

        /// <summary>
        /// Starts all stored profiles.
        /// </summary>
        public static void StartAll() {
#if DEBUG
        for(int i = 0; i < currentIndex + 1; i++) {
            Start(profiles[i]);
        }
#endif
        }

        /// <summary>
        /// Starts a new profile.
        /// </summary>
        /// <param name="profileName">Profile name.</param>
        /// <param name="reset">True to reset this value.</param>
        public static void Start(string profileName, bool reset) {
#if DEBUG
        if(reset) {
            Reset(profileName);
        }
        Start(profileName);
#endif
        }

        /// <summary>
        /// Starts a new profile.
        /// </summary>
        /// <param name="profileName">Profile name.</param>
        public static void Start(string profileName) {
#if DEBUG
        int index;

        if(!indices.TryGetValue(profileName, out index)) {
            index = ++currentIndex;
            profiles[index] = profileName;
            indices.Add(profileName, index);
        }

        count[index]++;
        Thread.Sleep(0);
        QueryPerformanceCounter(out startTime[index]);
#endif
        }

        /// <summary>
        /// Stops a running profile.
        /// </summary>
        /// <param name="profileName">Profile name.</param>
        public static void Stop(string profileName) {
#if DEBUG
        QueryPerformanceCounter(out stopTime);

        int index = indices[profileName];

        lastElapsed[index] = (stopTime - startTime[index]) * inverseFreq;
        totalElapsed[index] += lastElapsed[index];
#endif
        }

        public static double GetTime(string profileName) {
#if DEBUG
        int index;

        if(indices.TryGetValue(profileName, out index)) {
            return totalElapsed[index];
        }
#endif
            return 0;
        }

        public static double Difference(string profileNameA, string profileNameB) {
#if DEBUG
        return Math.Abs(GetTime(profileNameA) - GetTime(profileNameB));
#else
            return 0;
#endif
        }

        /// <summary>
        /// Returns a formated output string with stored profiling information. Numbers are returned in seconds.
        /// </summary>
        public static string OutputString() {
            string txt = string.Empty;
#if DEBUG
        for(int i = 0; i < currentIndex + 1; i++) {
            txt += profiles[i] + " -- Total: " + totalElapsed[i] + " - Last: " + lastElapsed[i] + " - Count: " + count[i] + "\n";
        }
#endif
            return txt;
        }

        /// <summary>
        /// Returns a formated output string with stored profiling information. Numbers are returned in seconds.
        /// </summary>
        /// <param name="tDigits">The number of digits to round the total-time to. Must be from 0 to 15.</param>
        /// <param name="lDigits">The number of digits to round the last-time to. Must be from 0 to 15.</param>
        public static string OutputString(int tDigits, int lDigits) {
            string txt = string.Empty;

#if DEBUG
        for(int i = 0; i < currentIndex + 1; i++) {
            txt += profiles[i] + " -- Total: " + Math.Round(totalElapsed[i], tDigits, MidpointRounding.AwayFromZero) + " - Last: " + Math.Round(lastElapsed[i], lDigits, MidpointRounding.AwayFromZero) + " - Count: " + count[i] + "\n";
        }
#endif

            return txt;
        }

        /// <summary>
        /// Returns a formated output string with stored profiling information. Numbers are returned in seconds.
        /// </summary>
        public static string OutputString(string profileName) {
#if DEBUG
        int index = indices[profileName];
        return profileName + " -- Total: " + totalElapsed[index] + " - Last: " + lastElapsed[index] + " - Count: " + count[index];
#else
            return string.Empty;
#endif
        }

        /// <summary>
        /// Returns a formated output string with stored profiling information. Numbers are returned in seconds.
        /// </summary>
        /// <param name="tDigits">The number of digits to round the total-time to. Must be from 0 to 15.</param>
        /// <param name="lDigits">The number of digits to round the last-time to. Must be from 0 to 15.</param>
        public static string OutputString(string profileName, int tDigits, int lDigits) {
#if DEBUG
        int index = indices[profileName];
        return profileName + " -- Total: " + Math.Round(totalElapsed[index], tDigits, MidpointRounding.AwayFromZero) + " - Last: " + Math.Round(lastElapsed[index], lDigits, MidpointRounding.AwayFromZero) + " - Count: " + count[index];
#else
            return string.Empty;
#endif
        }

        //private static long start;
        //private static long stop;
        //private static double inverseFrequency;
        //private static Decimal multiplier = new Decimal(1.0e9);

        //public static void Start() {
        //    QueryPerformanceCounter(out start);
        //}

        //public static void Stop() {
        //    QueryPerformanceCounter(out stop);
        //}

        //public double Duration(int iterations) {
        //    return (((double)(stop - start) * (double)multiplier) * inverseFrequency) / iterations;
        //}
    }
}